package org.dromara.web.service.impl

import cn.dev33.satoken.stp.StpUtil
import cn.hutool.core.util.ObjectUtil
import io.github.oshai.kotlinlogging.KotlinLogging
import me.zhyd.oauth.config.AuthConfig
import me.zhyd.oauth.model.AuthCallback
import me.zhyd.oauth.request.AuthRequest
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest
import org.dromara.common.core.domain.model.XcxLoginBody
import org.dromara.common.core.domain.model.XcxLoginUser
import org.dromara.common.core.enums.UserStatus
import org.dromara.common.core.exception.ServiceException
import org.dromara.common.core.utils.ValidatorUtils.validate
import org.dromara.common.json.utils.JsonUtils
import org.dromara.common.satoken.utils.LoginHelper
import org.dromara.system.domain.vo.SysClientVo
import org.dromara.system.domain.vo.SysUserVo
import org.dromara.web.domain.vo.LoginVo
import org.dromara.web.service.IAuthStrategy
import org.dromara.web.service.SysLoginService
import org.springframework.stereotype.Service

/**
 * 小程序认证策略
 *
 *@author LikeYouDo
 *@date 2025/1/5 21:26
 */
@Service("xcx" + IAuthStrategy.BASE_NAME)
class XcxAuthStrategy(
    private val loginService: SysLoginService
) : IAuthStrategy {
    companion object {
        private val log = KotlinLogging.logger {}
    }

    override fun login(body: String, client: SysClientVo): LoginVo {
        val loginBody = JsonUtils.parseObject(body, XcxLoginBody::class.java)
        validate(loginBody)
        // xcxCode 为 小程序调用 wx.login 授权后获取
        val xcxCode = loginBody.xcxCode
        // 多个小程序识别使用
        val appid = loginBody.appid

        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
        val authRequest: AuthRequest = AuthWechatMiniProgramRequest(
            AuthConfig.builder()
                .clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
                .ignoreCheckRedirectUri(true).ignoreCheckState(true).build()
        )
        val authCallback = AuthCallback()
        authCallback.code = xcxCode
        val resp = authRequest.login(authCallback)
        val openid: String?
        val unionId: String?
        if (resp.ok()) {
            val token = resp.getData().token
            openid = token.openId
            // 微信小程序只有关联到微信开放平台下之后才能获取到 unionId，因此unionId不一定能返回。
            unionId = token.unionId
        } else {
            throw ServiceException(resp.msg)
        }

        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
        val user = loadUserByOpenid(openid)
        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        val loginUser = XcxLoginUser().apply {
            tenantId = user.tenantId
            userId = user.userId
            username = user.userName
            nickname = user.nickName
            userType = user.userType
            clientKey = client.clientKey
            deviceType = client.deviceType
            this.openid = openid
        }

        val model = createSaLoginModel(client)
        // 生成token
        LoginHelper.login(loginUser, model)

        return LoginVo().apply {
            accessToken = StpUtil.getTokenValue()
            expireIn = StpUtil.getTokenTimeout()
            clientId = client.clientId
            this.openid = openid
        }
    }

    private fun loadUserByOpenid(openid: String): SysUserVo {
        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
        // todo 自行实现 userService.selectUserByOpenid(openid);
        val user = SysUserVo()
        if (ObjectUtil.isNull(user)) {
            log.info { "登录用户：$openid 不存在." }
            // todo 用户不存在 业务逻辑自行实现
        } else if (UserStatus.DISABLE.code == user.status) {
            log.info { "登录用户：$openid 已被停用." }
            // todo 用户已被停用 业务逻辑自行实现
        }
        return user
    }
}
